package com.zsk.tool.file;

import java.io.File;
import java.io.RandomAccessFile;

/**
 * 文件切割合并
 * 
 * @author keke
 * @date 2020/11/13
 */
public class FileSplitUtil {

    /**
     * 切割文件
     * 
     * @param filePath
     *            源文件路径
     * @param chunkSize
     *            每个块字节大小，指定为1024的整数倍
     * @return 总块数
     */
    public static int spliteFile(String filePath, long chunkSize) {

        File file = new File(filePath);
        long size = file.length();
        // 文件分割的份数
        int count = (int)(size % chunkSize == 0 ? size / chunkSize : size / chunkSize + 1);
        System.out.println("分割为" + count + "块");
        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
            // 初始化偏移量
            long offSet = 0L;
            // 最后一片单独处理
            for (int i = 0; i < count - 1; i++) {
                long begin = offSet;
                long end = (i + 1) * chunkSize;
                offSet = writeChunk(file, i, begin, end);
            }
            // 处理最后一片
            if (size - offSet > 0) {
                writeChunk(file, count - 1, offSet, size);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 单个块写入文件
     * 
     * @param file
     *            源文件
     * @param index
     *            块索引
     * @param begin
     *            字节起始位置
     * @param end
     *            字节结束为止
     * @return 最后边界字节为止
     */
    public static long writeChunk(File file, int index, long begin, long end) {
        long endPointer = 0L;

        // 申明文件切割后的文件磁盘
        try (RandomAccessFile in = new RandomAccessFile(file, "r");) {
            String chunkFilePath = file.getCanonicalPath() + "_" + index + ".tmp";
            try (// 定义一个可读，可写的文件并且后缀名为.tmp的二进制文件
                RandomAccessFile out = new RandomAccessFile(new File(chunkFilePath), "rw")) {
                // 1KB作为缓存
                byte[] b = new byte[1024];
                int n = 0;
                // 从指定位置读取文件字节流
                in.seek(begin);
                // 判断文件流读取的边界
                while (in.getFilePointer() < end && (n = in.read(b)) != -1) {
                    out.write(b, 0, n);
                }
                endPointer = in.getFilePointer();
                // 处理最后剩下的部分
                if (endPointer < end) {
                    int last = (int)(end - endPointer);
                    in.read(b, 0, last);
                    out.write(b, 0, last);
                }
                endPointer = in.getFilePointer();
                System.out.println("write: " + chunkFilePath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return endPointer;
    }

    /**
     * 文件合并
     * 
     * @param file
     *            指定合并文件
     * @param tempFile
     *            分割前的文件名
     * @param tempCount
     *            文件个数
     */
    public static void mergeChunkFile(String targetPath, File file, int chunkCount) {
        if (chunkCount == 0) {
            return;
        }
        File targetFile = new File(targetPath);
        File dirFile = targetFile.getParentFile();
        if (!dirFile.isDirectory()) {
            dirFile.mkdirs();
        }
        int mergeNum = 0;
        try (RandomAccessFile raf = new RandomAccessFile(targetFile, "rw");) {
            for (int i = 0; i < chunkCount; i++) {
                // 读取切片文件
                String chunkFilePath = file.getCanonicalPath() + "_" + i + ".tmp";
                try (RandomAccessFile reader = new RandomAccessFile(new File(chunkFilePath), "r")) {
                    byte[] b = new byte[1024];
                    int n = 0;
                    // 先读后写
                    while ((n = reader.read(b)) != -1) {
                        raf.write(b, 0, n);
                    }
                    mergeNum++;
                    System.out.println("read chunk: " + chunkFilePath + " success");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        if (mergeNum == chunkCount) {
            System.out.println("write: " + targetPath + " success");
        } else {
            System.out.println("write: " + targetPath + " failed");
        }
    }

    public static void main(String[] args) {
        String filePath = "D:/pool/1.dll";
        int chunkCount = spliteFile(filePath, 10 * 1024);
        mergeChunkFile("D:/pool/tmp/1.dll", new File(filePath), chunkCount);
    }

}
